﻿#pragma once

#include <boost/ptr_container/ptr_map.hpp>
#include <boost/signal.hpp>
#include <SFML/Network.hpp>
#include "PlayerClient.h"
#include "../models/Player.h"
#include "../configuration/NetworkConfiguration.h"


namespace Domain
{
	/**
	* Zawiera klasy obsługujące komunikację sieciową
	*/
	namespace Network
	{
		/**
		* Klasa klienta gry, zajmuje się komunikacją sieciową z serwerem (wysyłaniem i odbieraniem danych)
		*/
		class GameClient: private sf::Thread
		{
		public:
			typedef void OnReceiveData(sf::Packet);

		protected:
			bool is_running; /**< Oznacza, czy klient ma oczekiwać w pętli na wiadomości z serwera */
			sf::SocketTCP Socket; /**< Gniazdo, którym podłączony jest klient */
			PlayerClient* player_client; /**< Klient gracza */

			/** Typ przyporzadkowania typów pakietów do sygnałów */
			typedef std::map<Domain::Configuration::Network::PACKET_TYPE, boost::signal<OnReceiveData>*> ReceiveDataEventsMap;
			ReceiveDataEventsMap receive_data_events; /**< Przyporządkowanie typów pakietów do sygnałów */
		public:
			GameClient();
			GameClient(PlayerClient::PlayerId, sf::SocketTCP);
			~GameClient();
			
			/**
			* Łączy klienta z serwerem pod podanym adresem IP i zwraca się do niego o podanie identyfikatora gracza.
			* Wyrzuca wyjątek w razie niepowodzenia.
			* @param address Adres serwera
			* @raise Domain::Configuration::NetworkException
			*/
			void connect(sf::IPAddress);
			/**
			* @return Identyfikator gracza przypisanego do klienta
			*/
			PlayerClient::PlayerId getPlayerId();
			/**
			* @return Klient gracza przypisanego do tego klienta gry
			*/
			PlayerClient* getPlayerClient();
			/**
			* @return Prawda, jeśli klient gry ma przypisanego jakiegoś gracza
			*/
			bool hasPlayerSet();
			/**
			* Tworzy nowego klienta gracza i usuwa starego, jeśli był ustawiony
			* @param player Gracz, dla którego ma być utworzony klient gracza
			*/
			void setPlayer(Players::Player*);
			/**
			* Wysyła pakiet do serwera, uzupełniając pakiet informacjami o typie i wysyłającym graczu
			* @param packet_type Typ pakietu
			* @param packet Pakiet to wysłania.
			*/
			void send(Domain::Configuration::Network::PACKET_TYPE, sf::Packet);
			/**
			* Rozpoczyna wątek oczekujący na wiadomości od serwera
			*/
			void start();
			/**
			* Konczy wątek oczekujący na wiadomości od serwera
			*/
			void stop();

			/**
			* Podłącza slot do sygnału wyzwalanego po otrzymaniu od serwera pakietu o podanym typie.
			* Sygnał zostanie wywołany tylko raz (dla każdego slotu), a następnie zostanie zniszczony.
			* @param packet_type Typ pakietu
			* @param slot Funkcja wywoływana po otrzymaniu pakietu
			*/
			boost::signals::connection doOnReceiveData(Domain::Configuration::Network::PACKET_TYPE,
				const boost::signal<OnReceiveData>::slot_function_type&);

		protected:
	    	/**
			* Funkcja odziedziczona z sf::Thread, działająca w osobnym wątku.
			* Zawiera pętlę oczekująca na komunikaty od serwera, które przekazuje następnie do funkcji packetReceived(sf::Packet&)
			*/
			void Run();
			void deleteSignal(boost::signal<OnReceiveData>* signal); /**< Usuwa podany sygnał */
			/**
			* Odbiera pakiet, dekoduje informacje o jego nadawcy i typie, usuwa te informacje,
			* a następnie wywołuje odpowiednie sygnały
			*/
			void packetReceived(sf::Packet&);
			/**
			* Dodaje do kolejki zdarzeń aplikacji wywołanie sygnału o podanym typie,
			* a następnie wywołanie funkcji usuwającej ten sygnał.
			*/
			void receiveData(Domain::Configuration::Network::PACKET_TYPE, sf::Packet&);
			/**
			* Ustawia identyfikator gracza, odczytany z pakietu
			* @param packet Pakiet z informacją o identyfikatorze gracza
			*/
			void setPlayerId(sf::Packet&);
			/**
			* Wyzwala wyjątek sieci. Używane by przekazać do pętli zdarzeń aplikacji wyjątki wyłapane w wątku
			*/
			void raiseDisconnectionException();
		};
	}
}

